nginx 掛載gcs

睡睡念

今天要上QA測試了,然後我突然發現我之前測完沒留下筆記。
現在都快忘光了,還好最後有找回記憶。
來補一下了。

正文

今天要做的是把GCS變成一個圖片上傳空間,
可以用nginx來訪問。

前言

某個案子,要整個翻掉重做,這次要讓VM的機器跟GKE的pod能夠同時存取一個儲存空間,
預計使用Google Cloud Storage,踩了一個早上的坑。

正文

預計做兩個項目

  1. 在VM上面掛載gcs的資料夾

  2. 在GKE 上面的pod同時掛載 gcs的資料夾

  3. 在VM上面掛載gcs的資料夾
    安裝方式,目前我的vm是 RedHat 所以用下面的方式,CentOS也是用同樣的方式

  4. Configure the gcsfuse repo:

sudo tee /etc/yum.repos.d/gcsfuse.repo > /dev/null <<EOF
[gcsfuse]
name=gcsfuse (packages.cloud.google.com)
baseurl=https://packages.cloud.google.com/yum/repos/gcsfuse-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
       https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
  1. Install gcsfuse:
  sudo yum install gcsfuse

完成,其他安裝方式,請參考GoogleCloudPlatform/gcsfuse/install.md

登入使用,
這邊先使用 gcloud auth login 做測試
先建立資料夾 mkdir upload
掛載 gcsfuse my-bucket upload
卸除掛載 fusermount -u upload

Debug用

gcsfuse --foreground --debug_gcs --debug_http --debug_fuse --debug_invariants --key-file=/home/user/Downloads/my-key.json mybucket /upload

永久加入GOOGLE_APPLICATION_CREDENTIALS變數

開啟 /etc/profiles
新增 export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/my-key.json"
修改檔案後要想馬上生效還要執行#
source /etc/profile不然只能在下次重進此使用者時生效。

ref.Linux系統環境變數和別名設定(永久生效和臨時生效)
附註,json檔案的取得在 IAM角色內的服務賬戶
ref.創建和管理服務帳號密鑰
新增完GCS,記得把權限給服務賬戶。記得選擇完整權限(Fig. 1)

自動掛載

有權限了已後,再來就是自動掛載了。
到/etc/fstab上,輸入

my_bucket /home/ezio/upload gcsfuse key_file=key/key.json,rw,user,allow_other,uid=1008,gid=1009  0 0

重開機測試看看,收工。
ref.
How to use mount command in fstab file
gcsfuse automount on a non root user

查詢目前登入使用者的uid 跟 gid
id $(whoami)

如果要看全部使用者的話,
cat /etc/passwd

ref.Linux 的帳號與群組

  1. 在GKE 上面的pod同時掛載 gcs的資料夾
    基本的方式,
    先自己產生一個 image
FROM golang:1.14-alpine AS build-env
ENV GO111MODULE on

# WORKDIR /工作名錄名稱 當前的工作目錄名稱,若是不存在則會新建該目錄,
# 需要注意的是copy跟run的指令都是以WORKDIR為當前目錄下去跑的,
# 運用的時候需要注意相對位置。
WORKDIR $GOPATH/src

RUN go get -u github.com/googlecloudplatform/gcsfuse

COPY key.json .


FROM alpine:3.6
RUN apk add --no-cache ca-certificates fuse && rm -rf /tmp/*
COPY --from=build-env /go/bin/gcsfuse /usr/local/bin
COPY --from=build-env /go/src/key.json /
WORKDIR /

佈署 Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: gcs-fuse
    version: v1
  namespace: debug
  name: gcsfuse-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gcs-fuse
      version: v1
  template:
    metadata:
      labels:
        app: gcs-fuse
        version: v1
    spec:
      containers:
        - name: gcsfuse-test
          image: gcr.io/your-project/gcsfuse:v1.2
          # image: golang:1.14-alpine
          # command: [ "/bin/sh", "-c", "--" ]
          # args: [ "while true; do sleep 600; done;" ]
          securityContext:
            privileged: true
          command:
            - "/bin/sh"
            - "-c"
            - "while true; do mkdir /upload ; gcsfuse --key-file=key.json your-bucket /folder; sleep 600; done; "

如果跟現有的程式整合的話,
由於要執行多行指令,需使用shellScript的方式執行指令。
可能會遇到下列情況

"exec: "/init.sh": permission denied

在Dockerfile上修改權限

RUN chmod +x /init.sh

ref.
getting permission denied in docker run

exec user process caused "exec format error"

有人說在sh的頂端加上
#!/bin/bash
但我試了沒用,在猜想可能是alpine linux沒有bash導致
所以改用下面這個

ENTRYPOINT ["sh","/run.sh"]

ref. standard_init_linux.go:178: exec user process caused “exec format error”

執行sh時卡住,debug方式

用if else檢查

if mkdir /upload; then
    echo "mkdir directory! Success" 1>&2
    gcsfuse --key-file=key.json yellow-video /upload
else
    echo "Could not mkdir directory!" 1>&2
    exit 1
fi

另外,最後找出來原因是因為先執行go的程式,導致後續卡住,所以先建立資料夾後,再執行go

ref.
Shell Script 遇到錯誤時自動退出離開

ref.
身份驗證入門
Linux系統環境變數和別名設定(永久生效和臨時生效)

這篇有講到如何在deploy掛載GCS 通常就是要安裝一些東西。

會使用到gcsfuse這個指令,
所以我選擇直接建一個image,
然後掛載的時候直接用那個image就好。

gcs-download.json 是 要掛載GCS用的權限json檔

FROM nginx:latest
WORKDIR /app

COPY gcs-download.json .
#Start CloudStorage
RUN apt update && apt install -y gnupg lsb-release
RUN echo "deb https://packages.cloud.google.com/apt gcsfuse-$(lsb_release -c -s) main" |  tee /etc/apt/sources.list.d/gcsfuse.list
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg |  apt-key add -
RUN apt-get update && apt-get install -y gcsfuse
ENV GOOGLE_APPLICATION_CREDENTIALS=/app/gcs-download.json

WORKDIR /usr/share/nginx/html/files


然後部署的yaml,
這邊要注意的地方是
1.
nginx.conf 要加上 user root ,
這是因爲gcs掛載資料夾是用root的角色,所以如果nginx不用root啓動,
會出現403的權限問題。
ref. 四種解決Nginx出現403 forbidden 報錯的方法
2.
daemon off; 這部分指的是要讓nginx能夠在前景執行,不然pod會一直重開。
ref. nginx -g "daemon off;" 你學廢了嗎?

apiVersion: v1
kind: ConfigMap
metadata:
  name: systemfile-nginx-config
  namespace: default
data:
  nginx.conf: |
    user  root;
    worker_processes  auto;

    error_log  /var/log/nginx/error.log notice;
    pid        /var/run/nginx.pid;


    events {
        worker_connections  1024;
    }


    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';

        access_log  /var/log/nginx/access.log  main;

        sendfile        on;
        #tcp_nopush     on;

        keepalive_timeout  65;

        #gzip  on;

        include /etc/nginx/conf.d/*.conf;
    }

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: sms-systemfile-config
  namespace: default
data:
  default.conf: |
    server {
        listen           80  default_server;
        server_name      _;
        server_tokens    off;

        index index.html;

        location /files {
            alias        /usr/share/nginx/html/files;
            autoindex    on;
        }

        location / {
            root         /usr/share/nginx/html;
            index        index.html;
        }

        error_page       500 502 503 504  /50x.html;
        location = /50x.html {
            root         /usr/share/nginx/html;
        }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: systemmanageservice-systemfile
  namespace: default
  labels:
    group: systemmanageservice
    app: systemfile
spec:
  replicas: 1
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 15
  selector:
    matchLabels:
      group: systemmanageservice
      app: systemfile
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        group: systemmanageservice
        app: systemfile
    spec:
      containers:
        - name: systemmanageservice-systemfile
          image: gcs-nginx:0.0.2
          command:
            - /bin/bash
            - '-c'
            - >-
              gcsfuse systemfile-qa /usr/share/nginx/html/files && nginx -g
              "daemon off;";
          imagePullPolicy: Always # IfNotPresent, Always, Never
          securityContext:
            privileged: true
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - name: config-volume
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: default.conf
              readOnly: true
            - name: nginx-config-volume
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
              readOnly: true              
      volumes:
        - name: config-volume
          configMap:
            name: sms-systemfile-config
            items:
              - key: default.conf
                path: default.conf
            defaultMode: 420
        - name: nginx-config-volume
          configMap:
            name: systemfile-nginx-config
            items:
              - key: nginx.conf
                path: nginx.conf
            defaultMode: 420
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      schedulerName: default-scheduler
      securityContext: {}